“The great wall is made by blocks”
二进制加法
半加器
我们考虑最基本的二进制加法,两位相加,然后再扩展到高位上。对于一个对a, b
两位相加的加法器,我们称其为半加器,输入是a, b
两位,输出为和sum
以及进位carry
半加器实际上就是不考虑输入进位的加法器
全加器
现在我们考虑进位,引入全加器,即在输入端考虑有进位的情况,全加器的真值表如下:
c | b | a | sum(out) | carry(out) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
实际上全加器就是计算( a + b + carry),可以用两个半加器实现,实现方式如下:
多位加法器
现在我们来考虑如何实现多位加法,一样的道理,对于每一位运算,我们可以使用一个全加器,对于第一位,输入的carry设置为0即可,也不难,这里来直接给出HDL实现
1 | // This file is part of www.nand2tetris.org |
这个加法器的一个问题是进位信号在16个门之间传递,效率比较低,有一种方法叫做carry look ahead
,即超前进位加法器,优点是速度快,缺点是器件会增加很多:
负数
如果我们用最高位表示符号,即可得到负数,−x可以使用如下公式进行计算:
−(2n−x)例如对于二进制1001 (9),对应的负数为−(24−9)=(−7),我们称9为−7的补数,此处我们给出一个重要结论,两个负数相加可以表示为它们的补数相加:
−7+(−6)=−(24−9)+(−(24−10))=9+10由于我们抛弃了最高位,因此得到的补数和所需的负数结果相同。现在我们需要考虑给定一个数x,如何获取他的负数−x?这样我们就可以将减法转化为加法:y−x=y+(−x)。即我们需要如下芯片:
Input:x
Output:−x
这里我们运用一个小技巧:为了计算(2n−x),我们可以计算2n−x=1+(2n−1)−x,这样做的好处是2n−1全部为1,做减法不需要借位,只需要将被减数按位翻转即可,所以这里我们得到了最终的结论,很重要,记住:一个负数等于其正数按位翻转后+1
算数逻辑单元(ALU)
现在我们来构造CPU中最重要的模块:ALU,其模块图如下所示:
ALU:输入两个操作数和待执行的函数(算数或逻辑运算),给出指定结果,对于本课程的Hack ALU,其结构如下所示:
根据六个输入控制位zx, nx, zy, ny, f, no
,可以选择不同的操作,Hack ALU支持的操作如下:
输出立即数 | 一元逻辑运算 | 二元逻辑运算 | 数学运算 | |
---|---|---|---|---|
0 | x | x & y | -x | |
1 | y | x \ | y | -y |
-1 | !x | x+1 | ||
!y | y+1 | |||
x-1 | ||||
y-1 | ||||
x+y | ||||
x-y | ||||
y-x |
同时,该ALU还有两个输出控制位zr, ng
,表示输出是否为0或者为负数,ALU的HDL代码框架如下:
1 | Chip name: ALU |
控制位的真值表如下:
我们解释一下其中几行比较迷惑的
- 计算1:为了计算1,我们可以对齐按位取反,得到b’1110,而b’1110为−2,即−1+(−1),故只需要将x与y归零并按位取反即可
- 计算−x:−x=¯x+1,故−x−1=¯x,两边同时取反,可得¯−x−1=x,令x=−x,即可得到¯x−1=−x。
- 计算x−y: